#include "config.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#include <sys/file.h>
#include <sys/mman.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include <libgen.h>
#include <ctype.h>
#include <fcntl.h>
#include <libaio.h>
#include <sys/eventfd.h>
#include <errno.h>
#include <fcntl.h>

#define DBG_SUBSYS S_LIBREPLICA

#include "dbg.h"
#include "disk.h"

#define RAM_DISK_IO_CACHE_SEG_SIZE      (1 * 1024 * 1024 * 1024L)

/*
 * ramdisk 使用方式
 * 创建文件 raw_pool_disk_size_uuid
 * 1. ram
 * 2. pool_name
 * 3. disk_name
 * 4. size byte
 * 5. uuid
 * 并且和普通盘一样在/opt/fusionstack/data/disk/x.disk作相应的软链接
 */

static inline void* ram_disk_get_fd(const char *dev_name, uint64_t dev_size, const char *dev_uuid)
{
        int ret, i, fd;
        char path[128], *hp_dir = "/dev/hugepages";
        void *addr = NULL;
        uint64_t *addrs = NULL;

        DINFO("ram disk size %ju \n", dev_size);
        int memcache_seg_count = (int)(dev_size / RAM_DISK_IO_CACHE_SEG_SIZE);

        ret = ymalloc((void **)&addrs, sizeof(uint64_t) * memcache_seg_count);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        for (i = 0; i < memcache_seg_count; i++) {
                memset(path, 0x00, 128);
                snprintf(path, 128, "%s/ram_%s_%lu_%s_%d_%d", hp_dir, dev_name, dev_size, dev_uuid, memcache_seg_count, i);
                fd = open(path, O_RDWR, 0755);
                if(fd < 0 ) {   //errno == ??
                        fd = open(path, O_RDWR | O_LARGEFILE | O_EXCL | O_CREAT, 0755);
                        if(fd > 0) {   //create successfully.
                                ret = ftruncate(fd, RAM_DISK_IO_CACHE_SEG_SIZE);
                                if(ret)
                                        DERROR("turncate error %s[%d]:%s\n", strerror(ret), ret, path);
                        }
                }
                if(fd < 0){
                        ret = errno;
                        DERROR("%s[%d]:%s\n", strerror(ret), ret, path);
                        GOTO(err_free, ret);
                }

                addr = mmap(NULL, RAM_DISK_IO_CACHE_SEG_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
                if(addr == MAP_FAILED){
                        ret = errno;
                        close(fd);
                        DERROR("%s[%d]\n", strerror(ret), ret);
                        GOTO(err_free, ret);
                }

                close(fd);

                addrs[i] = (uint64_t)addr;
        }

        return (void *)addrs;
err_free:
        yfree((void **)&addrs);
err_ret:
        return NULL;
}

static int ram_disk_load_disk(disk_t *disk, const char *home, char *pool, uint64_t *disk_size)
{
        int ret, count = 5;
        void *fd;
        char *dev[5];
        char path[PATH_MAX];
        struct stat stbuf;
        char *pool_name, *disk_name, *uuid;

        (void) pool;
        YASSERT(disk->idx < DISK_MAX);

        sprintf(path, "%s/disk/%d.disk", home, disk->idx);

        ret = diskmd_real_path(path, &stbuf);
        if (ret) {
                GOTO(err_ret, ret);
        }

        DINFO("initior the ram device  %s\n", path);
        if (strstr(path, "ram") == NULL) {
                ret = ENODEV;
                GOTO(err_ret, ret);
        }

        _str_split(path, '_', dev, &count);

        if (count != 5) {
                DINFO("disk ram arg fail %d\n", count);
                return EINVAL;
        }

        // ram_poolname_diskname_disksize_uuid
        //"ram" == dev[0]
        pool_name = dev[1];
        disk_name = dev[2];
        *disk_size = strtol(dev[3], NULL, 10);
        uuid = dev[4];

        DINFO("ram disk pool %s name %s size %llu uuid %s\n", pool_name, disk_name, (LLU)*disk_size, uuid);
        fd = ram_disk_get_fd(pool_name, *disk_size, uuid);
        if(fd == NULL){
                ret = ENODEV;
                GOTO(err_ret, ret);
        }

        disk->disk_fd = fd;
        disk->disk_type = __DISK_TYPE_RAM_DISK__;

        strcpy(pool, pool_name);

        DINFO("load ram disk[%u] %s tier %d\n", disk->idx, disk_name, disk->tier);

        return 0;
err_ret:
        return ret;
}

static int ram_disk_create_new(disk_t *disk, const char *home, const char *pool)
{
        int ret;
        diskinfo_t diskinfo;
        char path[MAX_PATH_LEN];

        ret = disk_setinfo(home, pool, disk, &diskinfo);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        snprintf(path, MAX_PATH_LEN, "%s/info/%d.info", home, disk->idx);

        ret = _set_value(path, (void *)&diskinfo, sizeof(diskinfo), O_CREAT | O_TRUNC);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = bmap_set(&disk->bmap, 0);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = fsync(disk->map_fd);
        if (ret)
                UNIMPLEMENTED(__DUMP__);

        return 0;
err_ret:
        return ret;
}

static int ram_disk_probe_check(disk_t *disk, const char *home, const char *pool)
{
        int ret;
        diskinfo_t diskinfo, _diskinfo;
        char path[MAX_PATH_LEN], uuid[MAX_NAME_LEN], _uuid[MAX_NAME_LEN];

        if (disk->status & __DISK_OFFLINE__) {
                DWARN("disk[%u] offline\n", disk->idx);
                return 0;
        }

        if (bmap_get(&disk->bmap, 0) == 0) {
                DINFO("new disk[%u], need init\n", disk->idx);

                ret = disk->dop->create_new(disk, home, pool);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

        } else {
                ret = disk_getinfo(home, disk, &diskinfo);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                snprintf(path, MAX_PATH_LEN, "%s/info/%d.info", home, disk->idx);

                ret = _get_value(path, (void *)&_diskinfo, sizeof(_diskinfo));
                if (ret < 0) {
                        ret = -ret;
                        GOTO(err_ret, ret);
                }

                if (memcmp(&diskinfo, &_diskinfo, sizeof(diskinfo))) {
                        uuid_unparse(diskinfo.diskid, uuid);
                        uuid_unparse(_diskinfo.diskid, _uuid);
                        DWARN("bad disk, id %s : %s, cluster %s : %s, idx %u : %u\n",
                                        uuid, _uuid, diskinfo.cluster, _diskinfo.cluster,
                                        diskinfo.idx, _diskinfo.idx);
                        EXIT(EIO);
                }
        }

        return 0;
err_ret:
        return ret;
}


static void ram_disk_disk_destroy(disk_t *disk)
{
        munmap(disk->bmap.bits, disk->map_size);
        close(disk->map_fd);
        disk->disk_fd = NULL;
        //yfree((void**)&disk);
}

static void ram_disk_offline(disk_t *disk)
{
        disk->status |= __DISK_OFFLINE__;
        disk->disk_fd = NULL;
        yfree((void**)&disk);
}

static int ram_disk_aio_read(const disk_t *disk, const chkid_t *chkid, buffer_t *buf, off_t _offset, int prio)
{
        off_t new_offset;
        struct list_head *pos;
        seg_t *seg;
        uint64_t *_fd;
        uint64_t cache_fd;

        (void)chkid;
        (void)prio;

        _fd = (uint64_t *)disk->disk_fd;
        new_offset = /*offset + */_offset;
        cache_fd = _fd[(new_offset / RAM_DISK_IO_CACHE_SEG_SIZE)];

        YASSERT((new_offset + buf->len) <= ((new_offset / RAM_DISK_IO_CACHE_SEG_SIZE + 1) * RAM_DISK_IO_CACHE_SEG_SIZE));

        cache_fd = cache_fd + new_offset % RAM_DISK_IO_CACHE_SEG_SIZE;
        list_for_each(pos, &buf->list) {
                seg = (seg_t *)pos;

                memcpy(seg->handler.ptr, (void *)cache_fd, seg->len);
                cache_fd = cache_fd + seg->len;
        }

        return 0;
}

static int ram_disk_aio_write(const disk_t *disk, const chkid_t *chkid, const buffer_t *buf, off_t _offset, int prio)
{
        off_t new_offset;
        struct list_head *pos;
        seg_t *seg;
        uint64_t *_fd;
        uint64_t cache_fd;

        (void)chkid;
        (void)prio;

        _fd = (uint64_t *)disk->disk_fd;
        new_offset = /*offset + */_offset;
        cache_fd = _fd[(new_offset / RAM_DISK_IO_CACHE_SEG_SIZE)];

        YASSERT((new_offset + buf->len) <= ((new_offset / RAM_DISK_IO_CACHE_SEG_SIZE + 1) * RAM_DISK_IO_CACHE_SEG_SIZE));

        cache_fd = cache_fd + new_offset % RAM_DISK_IO_CACHE_SEG_SIZE;
        list_for_each(pos, &buf->list) {
                seg = (seg_t *)pos;

                memcpy((void *)cache_fd, seg->handler.ptr, seg->len);
                cache_fd = cache_fd + seg->len;
        }

        return 0;
}
/*
static int ram_disk_io_pwritev(const disk_t *disk, const  buffer_t *buf, off_t _offset)
{
        struct list_head *pos;
        seg_t *seg;
        uint64_t *_fd = (uint64_t *)disk->disk_fd;
        uint64_t cache_fd = _fd[(_offset / RAM_DISK_IO_CACHE_SEG_SIZE)];

        YASSERT((_offset + buf->len) <= ((_offset / RAM_DISK_IO_CACHE_SEG_SIZE + 1) * RAM_DISK_IO_CACHE_SEG_SIZE));

        cache_fd = cache_fd + _offset % RAM_DISK_IO_CACHE_SEG_SIZE;
        list_for_each(pos, &buf->list) {
                seg = (seg_t *)pos;

                memcpy((void *)cache_fd, seg->handler.ptr, seg->len);
                cache_fd = cache_fd + seg->len;
        }

        return 0;
}

static int ram_disk_io_preadv(const disk_t *disk, buffer_t *buf, off_t  _offset)
{
        struct list_head *pos;
        seg_t *seg;
        uint64_t *_fd = (uint64_t *)disk->disk_fd;
        uint64_t cache_fd = _fd[(_offset / RAM_DISK_IO_CACHE_SEG_SIZE)];

        YASSERT((_offset + buf->len) <= ((_offset / RAM_DISK_IO_CACHE_SEG_SIZE + 1) * RAM_DISK_IO_CACHE_SEG_SIZE));

        cache_fd = cache_fd + _offset % RAM_DISK_IO_CACHE_SEG_SIZE;
        list_for_each(pos, &buf->list) {
                seg = (seg_t *)pos;

                memcpy(seg->handler.ptr, (void *)cache_fd, seg->len);
                cache_fd = cache_fd + seg->len;
        }

        return 0;
}
*/
static int ram_disk_io_pread(const disk_t *disk, char *buf, size_t size,  off_t offset)
{
        uint64_t *_fd = (uint64_t *)disk->disk_fd;
        uint64_t cache_fd = _fd[(offset / RAM_DISK_IO_CACHE_SEG_SIZE)];

        YASSERT((offset + (off_t)size) <= ((offset / RAM_DISK_IO_CACHE_SEG_SIZE + 1) * RAM_DISK_IO_CACHE_SEG_SIZE));

        cache_fd = cache_fd + offset % RAM_DISK_IO_CACHE_SEG_SIZE;
        memcpy(buf, (void *)cache_fd, size);

        return size;
}

static int ram_disk_io_pwrite(const disk_t *disk, char *buf, size_t size,  off_t offset)
{
        uint64_t *_fd = (uint64_t *)disk->disk_fd;
        uint64_t cache_fd = _fd[(offset / RAM_DISK_IO_CACHE_SEG_SIZE)];

        YASSERT((offset + (off_t)size) <= ((offset / RAM_DISK_IO_CACHE_SEG_SIZE + 1) * RAM_DISK_IO_CACHE_SEG_SIZE));

        cache_fd = cache_fd + offset % RAM_DISK_IO_CACHE_SEG_SIZE;

        memcpy((void *)cache_fd, buf, size);

        return size;
}

int ram_disk_writeable(disk_t *disk)
{
        (void)disk;

        return 1;
}


static int ram_disk_connect(const disk_t *disk, disk_t *newdisk)
{
        int ret;

        ret = ymalloc((void **)&newdisk, sizeof(*newdisk));
        if (unlikely(ret))
                GOTO(err_ret, ret);
        
        newdisk->disk_fd = disk->disk_fd;
        newdisk->disk_type = __DISK_TYPE_RAM_DISK__;
        newdisk->idx = disk->idx;
        newdisk->dop = disk->dop;

        DINFO("load ram disk[%u]\n", disk->idx);

        return 0;
err_ret:
        return ret;
}

static void ram_disk_disconnect(disk_t *disk)
{
        (void) disk;
}

static struct disk_op_t __ram_disk_dop__ = {
        .create_new = ram_disk_create_new,
        .probe_check = ram_disk_probe_check,
        .offline = ram_disk_offline,

        .open = ram_disk_load_disk,
        .close = ram_disk_disk_destroy,
        .io_pread = ram_disk_io_pread,
        .io_pwrite = ram_disk_io_pwrite,
        //.io_preadv = ram_disk_io_preadv,
        //.io_pwritev = ram_disk_io_pwritev,
        .aio_readv = ram_disk_aio_read,
        .aio_writev = ram_disk_aio_write,
        .writeable = ram_disk_writeable,
        .connect = ram_disk_connect,
        .disconnect = ram_disk_disconnect,
};


struct disk_op_t * get_ram_disk_ops()
{
        return &__ram_disk_dop__;
}
